Skip to content

feat(web): self-serve forgot/reset-password flow - #20

Merged
Lingz450 merged 1 commit into
mainfrom
feat/web-forgot-password
Jun 24, 2026
Merged

feat(web): self-serve forgot/reset-password flow#20
Lingz450 merged 1 commit into
mainfrom
feat/web-forgot-password

Conversation

@Lingz450

Copy link
Copy Markdown
Collaborator

What

Adds a complete self-serve forgot password flow on top of the existing local email+password auth.

Backend (app/)

  • core/password_reset.py - opaque, single-use, expiring reset-token store mirroring refresh_tokens (SHA-256 at rest, memory dev / Redis prod, atomic consume). TTL via password_reset_ttl_minutes (default 30).
  • POST /auth/forgot-password - enumeration-safe: identical neutral 200 for known/unknown email; only mints a token + sends mail for a real active account; internal errors swallowed so they can't be an existence oracle.
  • POST /auth/reset-password - validates password, single-use consume, set_password, clears lockout, signs the user straight in (same {token, refresh_token, principal} as signin).
  • core/email.py::send_password_reset_email via Resend; never raises.
  • Both routes added to the IP-keyed auth rate limiter.

Frontend (apps/web/)

  • requestPasswordReset / resetPassword API clients.
  • "Forgot password?" link on the signin form (signin only).
  • /forgot-password and /reset-password pages reusing the AuthForm visual shell.

Tests

tests/test_auth_password_reset.py - neutral responses, single-use token, sign-in afterward, invalid/weak-token rejection. Frontend typecheck + lint + production build all pass.

Notes

  • Emails only send once the Resend domain is verified and PB_RESEND_API_KEY is set in Render (same prerequisite as invites); the token is still valid before then.
  • Reset does not revoke existing sessions (refresh store is hash-keyed, can't enumerate per-user); access tokens are 1h so exposure is bounded.

🤖 Generated with Claude Code

Backend:
- New password_reset token store (opaque, single-use, expiring) mirroring
  refresh_tokens; TTL via password_reset_ttl_minutes (default 30).
- POST /auth/forgot-password (enumeration-safe, neutral 200) and
  POST /auth/reset-password (single-use consume, set_password, sign in).
- Resend password-reset email; both routes added to the auth rate limiter.

Frontend:
- requestPasswordReset/resetPassword clients, "Forgot password?" link on
  signin, and /forgot-password + /reset-password pages.

Tests: tests/test_auth_password_reset.py (neutral responses, single-use,
sign-in afterward, invalid/weak-token rejection).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
petrobrain Ready Ready Preview, Comment Jun 24, 2026 8:29am

@Lingz450
Lingz450 merged commit 6ca8678 into main Jun 24, 2026
7 checks passed
@Lingz450
Lingz450 deleted the feat/web-forgot-password branch June 24, 2026 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant